#include <bits/stdc++.h>
using namespace std;
using Int = long long;
using _int = int;
using ll = long long;
using Double = long double;
const Int INF = (1LL << 60) + 1e9;
const Int mod = (1e9) + 7;
const Double EPS = 1e-8;
const Double PI = 6.0 * asin((Double)0.5);
using P = pair<Int, Int>;
template <class T>
T Max(T& a, T b) {
  return a = max(a, b);
}
template <class T>
T Min(T& a, T b) {
  return a = min(a, b);
}
template <class T1, class T2>
ostream& operator<<(ostream& o, pair<T1, T2> p) {
  return o << "(" << p.first << "," << p.second << ")";
}
template <class T1, class T2, class T3>
ostream& operator<<(ostream& o, tuple<T1, T2, T3> t) {
  return o << "(" << get<0>(t) << "," << get<1>(t) << "," << get<2>(t) << ")";
}
template <class T1, class T2>
istream& operator>>(istream& i, pair<T1, T2>& p) {
  return i >> p.first >> p.second;
}
template <class T>
ostream& operator<<(ostream& o, vector<T> a) {
  Int i = 0;
  for (T t : a) o << (i++ ? " " : "") << t;
  return o;
}
template <class T>
istream& operator>>(istream& i, vector<T>& a) {
  for (T& t : a) i >> t;
  return i;
}
template <typename ctype, ctype INF>
class Dijkstra {
 public:
  typedef tuple<Int, ctype> T;
  typedef tuple<ctype, Int> T2;
  Int V;
  vector<vector<T> > G;
  vector<vector<T> > rG;
  Dijkstra() : V(-1) {}
  Dijkstra(Int V) : V(V), G(V), rG(V) {}
  void add_edge(Int a, Int b, ctype c, Int oneway = 0) {
    assert(a >= 0 && b >= 0);
    assert(a < V && b < V);
    G[a].push_back(T(b, c));
    rG[b].push_back(T(a, c));
    if (!oneway) G[b].push_back(T(a, c)), rG[a].push_back(T(b, c));
  }
  vector<ctype> dijkstra(Int start, Int rev = 0) {
    vector<ctype> D(V, INF);
    vector<Int> visited(V, 0);
    priority_queue<T2, vector<T2>, greater<T2> > Q;
    Q.push(T2(0, start));
    D[start] = 0;
    while (!Q.empty()) {
      ctype cost;
      Int pos;
      tie(cost, pos) = Q.top();
      Q.pop();
      assert(!visited[pos] || D[pos] <= cost);
      if (visited[pos]++) continue;
      for (auto t : rev == 0 ? G[pos] : rG[pos]) {
        Int to = get<0>(t);
        ctype ncost = cost + get<1>(t);
        if (D[to] <= ncost) continue;
        D[to] = ncost;
        Q.push(T2(ncost, to));
      }
    }
    return D;
  }
  vector<vector<Int> > getDAG(Int start, Int goal) {
    auto DS = dijkstra(start, 0);
    auto DT = dijkstra(goal, 1);
    vector<vector<Int> > DAG(V);
    vector<Int> visited(V, 0);
    queue<Int> Q;
    Q.push(start);
    while (!Q.empty()) {
      Int pos;
      pos = Q.front();
      Q.pop();
      if (visited[pos]++) continue;
      for (auto t : G[pos]) {
        Int to = get<0>(t);
        ctype cost = get<1>(t);
        if (DS[pos] + DT[to] + cost != DS[goal]) continue;
        Q.push(to);
        DAG[pos].push_back(to);
      }
    }
    return DAG;
  }
};
signed main() {
  srand((unsigned)time(NULL));
  cin.tie(0);
  ios_base::sync_with_stdio(0);
  cout << fixed << setprecision(12);
  Int H, W;
  cin >> H >> W;
  vector<string> mp(H);
  cin >> mp;
  Int dx[] = {0, 0, 1, -1};
  Int dy[] = {1, -1, 0, 0};
  Dijkstra<Int, INF> dij(H * W);
  for (Int i = 0; i < H; i++)
    for (Int j = 0; j < W; j++) {
      for (Int k = 0; k < 4; k++) {
        Int ni = i + dy[k];
        Int nj = j + dx[k];
        if (ni < 0 || ni >= H || nj < 0 || nj >= W || mp[i][j] == '#' ||
            mp[ni][nj] == '#')
          continue;
        Int a = i * W + j;
        Int b = ni * W + nj;
        dij.add_edge(a, b, 1, 1);
      }
    }
  Int ans = 0;
  for (Int i = 0; i < H; i++)
    for (Int j = 0; j < W; j++) {
      if (mp[i][j] == '#') continue;
      Int S = i * W + j;
      auto D = dij.dijkstra(S);
      for (Int s = 0; s < H; s++)
        for (Int t = 0; t < H; t++) {
          if (mp[s][t] == '#') continue;
          Int T = s * W + t;
          Max(ans, D[T]);
        }
    }
  cout << ans << endl;
  return 0;
}
